#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
文件指纹修改器
修改文件的MD5和Hash值，避免文件检查
整合了原 ImageObfuscator 的功能
"""

import os
import hashlib
import random
import string
import mimetypes
import struct
import plistlib
import shutil
import json
import xml.etree.ElementTree as ET
from typing import Optional, Dict, Tuple, List
from pathlib import Path
from ..builtin_config_module.builtin_config import BuiltInConfig
from ..utils_module.logger import ObfuscationLogger
from ..utils_module.language_utils import SUPPORTED_LANGUAGES, collect_extensions
from PIL import Image, PngImagePlugin, ExifTags

class FileFingerprintModifier:
    """
    统一的文件指纹修改器

    整合了原 ImageObfuscator 的所有功能，提供统一的文件指纹修改接口
    支持文本、图片、二进制、源代码、资源文件等多种文件类型
    """

    def __init__(self, config: BuiltInConfig, logger: ObfuscationLogger):
        self.config = config
        self.logger = logger

        # 备份文件映射（从ImageObfuscator迁移）
        self.backup_map = {}

        # 文件类型和修改策略
        self.modification_strategies = {
            'text': self._modify_text_file,
            'image': self._modify_image_file,
            'binary': self._modify_binary_file,
            'source': self._modify_source_file,
            'resource': self._modify_resource_file
        }

        # 支持的图片格式（整合ImageObfuscator的格式）
        self.supported_image_formats = [
            '.png', '.jpg', '.jpeg', '.gif', '.bmp',
            '.tiff', '.svg', '.webp', '.ico'
        ]

        language_source_extensions = collect_extensions(SUPPORTED_LANGUAGES)
        extra_source_extensions = {
            '.cpp', '.c', '.py', '.js', '.ts', '.jsx', '.tsx'
        }

        # 文件类型映射
        self.file_type_mapping: Dict[str, str] = {}

        for ext in language_source_extensions.union(extra_source_extensions):
            self.file_type_mapping[ext] = 'source'

        self.file_type_mapping.update({
            # 图片文件
            '.png': 'image',
            '.jpg': 'image',
            '.jpeg': 'image',
            '.gif': 'image',
            '.bmp': 'image',
            '.tiff': 'image',
            '.webp': 'image',
            '.svg': 'image',
            '.ico': 'image',

            # 资源文件
            '.xml': 'resource',
            '.json': 'resource',
            '.plist': 'resource',
            '.yaml': 'resource',
            '.yml': 'resource',
            '.properties': 'resource',
            '.cfg': 'resource',
            '.conf': 'resource',
            '.ini': 'resource',

            # 文本文件
            '.txt': 'text',
            '.md': 'text',
            '.rst': 'text',
            '.log': 'text',

            # 二进制文件
            '.a': 'binary',
            '.so': 'binary',
            '.dylib': 'binary',
            '.framework': 'binary',
            '.jar': 'binary',
            '.aar': 'binary',
            '.apk': 'binary',
            '.ipa': 'binary'
        })

    # ==================== 从 ImageObfuscator 迁移的方法 ====================

    def modify_image_md5(self, target_dir: str = None):
        """
        修改图片MD5值（保持向后兼容）

        这是从 ImageObfuscator 迁移的主要接口
        """
        if not target_dir:
            target_dir = self._find_assets_directory()

        if not target_dir or not os.path.exists(target_dir):
            self.logger.log_warning("未找到Assets.xcassets目录")
            return

        self.logger.log_operation("开始修改图片MD5", f"目标目录: {target_dir}")

        # 查找所有图片文件
        image_files = self._find_image_files(target_dir)

        for image_file in image_files:
            try:
                # 创建备份
                self._create_backup(image_file)

                # 修改指纹
                self.modify_file_fingerprint(image_file, strategy='image')

            except Exception as e:
                self.logger.log_error(f"修改图片MD5失败: {image_file}, 错误: {str(e)}")

    def _find_assets_directory(self) -> str:
        """查找Assets.xcassets目录（从ImageObfuscator迁移）"""
        # 递归查找Assets.xcassets目录
        for root, dirs, files in os.walk(self.config.project_path):
            if 'Assets.xcassets' in dirs:
                assets_path = os.path.join(root, 'Assets.xcassets')
                self.logger.log_operation("找到Assets目录", assets_path)
                return assets_path

        # 备用方案：使用glob模式
        import glob
        pattern = os.path.join(self.config.project_path, '**/Assets.xcassets')
        matches = glob.glob(pattern, recursive=True)
        if matches:
            self.logger.log_operation("找到Assets目录", matches[0])
            return matches[0]

        return None

    def _find_image_files(self, directory: str) -> List[str]:
        """查找目录下的所有图片文件（从ImageObfuscator迁移）"""
        image_files = []

        for root, dirs, files in os.walk(directory):
            for file in files:
                if any(file.lower().endswith(ext) for ext in self.supported_image_formats):
                    image_files.append(os.path.join(root, file))

        return image_files

    def _create_backup(self, file_path: str):
        """创建文件备份（从ImageObfuscator迁移）"""
        backup_path = f"{file_path}.backup"
        if not os.path.exists(backup_path):
            shutil.copy2(file_path, backup_path)
            self.backup_map[file_path] = backup_path
            self.logger.log_operation("创建备份", f"{file_path} -> {backup_path}")

    def restore_backups(self):
        """恢复所有备份文件（新增功能）"""
        for original, backup in self.backup_map.items():
            if os.path.exists(backup):
                try:
                    shutil.copy2(backup, original)
                    os.remove(backup)
                    self.logger.log_operation("恢复备份", f"{backup} -> {original}")
                except Exception as e:
                    self.logger.log_error(f"恢复备份失败: {original}, 错误: {str(e)}")

    # ==================== 原有的核心功能 ====================

    def modify_file_fingerprint(self, file_path: str, strategy: Optional[str] = None) -> bool:
        """
        修改文件指纹

        Args:
            file_path: 文件路径
            strategy: 修改策略，如果不指定则自动判断

        Returns:
            是否成功修改
        """
        if not os.path.exists(file_path):
            self.logger.log_warning(f"文件不存在: {file_path}")
            return False

        if self.config.dry_run:
            self.logger.log_operation("模拟修改指纹", f"文件: {file_path}")
            return True

        # 获取文件的原始MD5
        original_md5 = self._calculate_file_md5(file_path)

        # 确定文件类型和修改策略
        if not strategy:
            file_type = self._determine_file_type(file_path)
            strategy = file_type

        # 获取修改函数
        modify_func = self.modification_strategies.get(
            strategy,
            self._modify_generic_file
        )

        try:
            # 执行修改
            success = modify_func(file_path)

            if success:
                # 验证MD5已改变
                new_md5 = self._calculate_file_md5(file_path)
                if new_md5 != original_md5:
                    self.logger.log_file_processing(
                        file_path,
                        f"指纹已修改: {original_md5[:8]}... -> {new_md5[:8]}..."
                    )
                    return True
                else:
                    self.logger.log_warning(f"文件指纹未改变: {file_path}")
                    return False
            else:
                return False

        except Exception as e:
            self.logger.log_error(f"修改文件指纹失败: {file_path}, 错误: {str(e)}")
            return False

    def batch_modify_fingerprints(self, directory: str, recursive: bool = True) -> Dict[str, bool]:
        """
        批量修改目录中文件的指纹

        Args:
            directory: 目录路径
            recursive: 是否递归处理子目录

        Returns:
            {文件路径: 是否成功} 的字典
        """
        results = {}

        if recursive:
            for root, dirs, files in os.walk(directory):
                # 跳过排除目录
                dirs[:] = [d for d in dirs if d not in self.config.file_filters.exclude_directories]

                for file in files:
                    file_path = os.path.join(root, file)
                    results[file_path] = self.modify_file_fingerprint(file_path)
        else:
            for file in os.listdir(directory):
                file_path = os.path.join(directory, file)
                if os.path.isfile(file_path):
                    results[file_path] = self.modify_file_fingerprint(file_path)

        # 统计结果
        success_count = sum(1 for success in results.values() if success)
        total_count = len(results)

        self.logger.log_operation(
            "批量指纹修改完成",
            f"成功: {success_count}/{total_count}"
        )

        return results

    def _calculate_file_md5(self, file_path: str) -> str:
        """计算文件的MD5值"""
        md5_hash = hashlib.md5()
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b''):
                md5_hash.update(chunk)
        return md5_hash.hexdigest()

    def _determine_file_type(self, file_path: str) -> str:
        """判断文件类型"""
        # 获取文件扩展名
        _, ext = os.path.splitext(file_path)
        ext = ext.lower()

        # 从映射表中查找
        return self.file_type_mapping.get(ext, 'binary')

    def _generate_random_string(self, length: int = 16) -> str:
        """生成随机字符串"""
        return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

    # ==================== 各类型文件的修改策略 ====================

    def _modify_text_file(self, file_path: str) -> bool:
        """修改文本文件"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()

            # 添加不可见的Unicode字符
            zero_width_space = '\u200b'
            zero_width_joiner = '\u200d'

            # 在内容末尾添加不可见字符序列
            random_suffix = ''.join(random.choices([zero_width_space, zero_width_joiner], k=8))
            content += random_suffix

            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)

            return True
        except Exception as e:
            self.logger.log_error(f"修改文本文件失败: {e}")
            return False

    def _modify_image_file(self, file_path: str) -> bool:
        """
        修改图片文件 - 增强版本

        整合了原 ImageObfuscator 的功能，提供更全面的图片处理
        """
        try:
            # 读取原始MD5
            with open(file_path, 'rb') as f:
                original_data = f.read()
                original_md5 = hashlib.md5(original_data).hexdigest()

            # 处理SVG特殊情况
            if file_path.lower().endswith('.svg'):
                return self._modify_svg_file(file_path)

            # 使用Pillow打开图片
            img = Image.open(file_path)
            img_format = img.format

            # 生成随机字符串用于元数据
            random_str = f"Obfuscated_{random.randint(10000, 99999)}_{self._generate_random_string(8)}"

            # 根据格式修改元数据
            if img_format == 'PNG':
                # 为PNG添加文本元数据（增强版本）
                metadata = PngImagePlugin.PngInfo()
                metadata.add_text("Fingerprint", random_str)
                metadata.add_text("Obfuscation", f"Modified_{random.randint(10000, 99999)}")
                metadata.add_text("Timestamp", str(random.randint(1000000000, 9999999999)))
                img.save(file_path, format='PNG', pnginfo=metadata)

            elif img_format in ['JPEG', 'JPG']:
                # 为JPEG修改EXIF数据（增强版本）
                exif = img.getexif()
                exif[0x9286] = random_str  # UserComment
                exif[0x9003] = f"{random.randint(2020, 2024)}:01:01 00:00:00"  # DateTimeOriginal
                # 微调质量来进一步改变文件
                quality = random.randint(95, 100)
                img.save(file_path, format='JPEG', exif=exif, quality=quality, optimize=True)

            elif img_format == 'GIF':
                # 为GIF添加注释
                img.info['comment'] = random_str
                img.info['timestamp'] = str(random.randint(1000000000, 9999999999))
                img.save(file_path, format='GIF', save_all=True)

            elif img_format == 'WEBP':
                # WebP格式处理
                img.info['icc_profile'] = b'Modified'
                img.save(file_path, format='WEBP', lossless=True)

            else:
                # 其他格式，通用处理
                if hasattr(img, 'info'):
                    img.info['modified'] = random_str

                # 根据格式决定保存参数
                save_kwargs = {'format': img_format}
                if img_format in ['JPEG', 'JPG']:
                    save_kwargs['quality'] = random.randint(95, 100)
                    save_kwargs['optimize'] = True

                img.save(file_path, **save_kwargs)

            # 验证修改
            with open(file_path, 'rb') as f:
                modified_data = f.read()
                modified_md5 = hashlib.md5(modified_data).hexdigest()

            if original_md5 != modified_md5:
                self.logger.log_operation(
                    "图片指纹修改成功",
                    f"{file_path}, MD5: {original_md5[:8]}... -> {modified_md5[:8]}..."
                )
                return True
            else:
                self.logger.log_warning(f"图片指纹未改变: {file_path}")
                return False

        except Exception as e:
            self.logger.log_error(f"修改图片文件失败: {e}")
            return False

    def _modify_svg_file(self, file_path: str) -> bool:
        """修改SVG文件（特殊处理）"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()

            # 在SVG中添加注释
            random_comment = f"<!-- Obfuscated: {self._generate_random_string()} -->"

            # 找到合适的插入位置
            if '<svg' in content:
                index = content.find('<svg')
                index = content.find('>', index) + 1
                content = content[:index] + '\n' + random_comment + content[index:]
            else:
                content = random_comment + '\n' + content

            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)

            return True
        except Exception as e:
            self.logger.log_error(f"修改SVG文件失败: {e}")
            return False

    def _modify_binary_file(self, file_path: str) -> bool:
        """修改二进制文件"""
        try:
            with open(file_path, 'rb') as f:
                data = f.read()

            # 在文件末尾添加随机字节
            random_bytes = os.urandom(random.randint(16, 32))
            data += random_bytes

            with open(file_path, 'wb') as f:
                f.write(data)

            return True
        except Exception as e:
            self.logger.log_error(f"修改二进制文件失败: {e}")
            return False

    def _modify_source_file(self, file_path: str) -> bool:
        """修改源代码文件"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()

            # 确定注释格式
            _, ext = os.path.splitext(file_path)
            ext = ext.lower()

            if ext in ['.py', '.sh', '.rb', '.pl', '.yml', '.yaml']:
                comment_prefix = '#'
            elif ext in ['.java', '.kt', '.swift', '.js', '.ts', '.cpp', '.c', '.h', '.m', '.mm']:
                comment_prefix = '//'
            elif ext in ['.html', '.xml']:
                comment_prefix = '<!--'
                comment_suffix = '-->'
            else:
                comment_prefix = '//'

            # 生成随机注释
            random_comment = f"{comment_prefix} Fingerprint: {self._generate_random_string()}"

            if 'comment_suffix' in locals():
                random_comment += comment_suffix

            # 在文件末尾添加注释
            content += '\n' + random_comment + '\n'

            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)

            return True
        except Exception as e:
            self.logger.log_error(f"修改源文件失败: {e}")
            return False

    def _modify_resource_file(self, file_path: str) -> bool:
        """修改资源文件"""
        _, ext = os.path.splitext(file_path)
        ext = ext.lower()

        if ext == '.json':
            return self._modify_json_file(file_path)
        elif ext == '.xml':
            return self._modify_xml_file(file_path)
        elif ext == '.plist':
            return self._modify_plist_file(file_path)
        else:
            # 默认当作文本文件处理
            return self._modify_text_file(file_path)

    def _modify_json_file(self, file_path: str) -> bool:
        """修改JSON文件"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                data = json.load(f)

            # 添加指纹字段
            data['__fingerprint__'] = self._generate_random_string()

            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)

            return True
        except Exception as e:
            # 如果JSON解析失败，当作文本处理
            return self._modify_text_file(file_path)

    def _modify_xml_file(self, file_path: str) -> bool:
        """修改XML文件"""
        try:
            tree = ET.parse(file_path)
            root = tree.getroot()

            # 添加注释
            comment = ET.Comment(f" Fingerprint: {self._generate_random_string()} ")
            root.insert(0, comment)

            tree.write(file_path, encoding='utf-8', xml_declaration=True)
            return True
        except Exception as e:
            # 如果XML解析失败，当作文本处理
            return self._modify_text_file(file_path)

    def _modify_plist_file(self, file_path: str) -> bool:
        """修改plist文件"""
        try:
            with open(file_path, 'rb') as f:
                data = plistlib.load(f)

            # 添加指纹字段
            data['__fingerprint__'] = self._generate_random_string()

            with open(file_path, 'wb') as f:
                plistlib.dump(data, f)

            return True
        except Exception as e:
            # 如果plist解析失败，当作文本处理
            return self._modify_text_file(file_path)

    def _modify_generic_file(self, file_path: str) -> bool:
        """通用文件修改策略"""
        # 根据文件内容判断是文本还是二进制
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                f.read(1024)
            # 能读取说明是文本文件
            return self._modify_text_file(file_path)
        except UnicodeDecodeError:
            # 不能读取说明是二进制文件
            return self._modify_binary_file(file_path)